package com.Liyunhan.design;

import java.io.File;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

/**
 * @Description: 几何分布   X ~ G(P)
 * ------------------------------------>>模拟实验<<-------------------------------------
 * 1. 使用java.Random类生成随机数(线性同余法)
 * 2. 几何分布: P{X=k} = (1-p)^(k-1)*p  "共k次实验, 第k次'成功'的概率"
 * 3. 对象实例化要求传入n和p
 * 4. 使用伪随机数"轮盘落点法": 落在 0~p内为"成功", p~1内为"失败"
 * 5. 共进行n次这样的实验, 使用map记录每一次实验
 *      如: 第1次"成功", 在n次实验中共发生a次;
 *      第r次"成功", 在n次实验中共发生b次;
 *      以键值对形式存入一个hashmap中 ({"第一次"成功" : 3"})
 * 6. 计算某次成功与总次数的比值, 即得出模拟的概率, 再与公式算出的概率进行拟合
 *
 * @Author: Liyunhan
 * @Date: 2021/10/5 23:38
 */
public class GeometricDistribution implements Serializable {
    //封装几何分布概率计算公式的结果
    private ArrayList formulaList;
    //记录事件:第k次"成功"发生的次数 >> {"k=i", x}
    private HashMap<String, Integer> expMap = new HashMap<>();
    //记录实验模拟的每个结果的概率
    private ArrayList expList;

    //试验成功概率为p, 不成功为1-p, p∈[0, 1]
    private double p;
    //实验模拟总次数, n > 0
    private int n;

    /**
     * 仅提供含参构造函数, 要求必须输入n, p
     * 从参数列表中获取超参数
     * 初始化 n p
     * @param hyperparams
     * @param simuResults
     * @param calResults
     */
    public GeometricDistribution(ArrayList hyperparams, ArrayList simuResults, ArrayList calResults) {
        this.n = (int)hyperparams.get(0);
        this.p = (double)hyperparams.get(1);
        this.expList = simuResults;
        this.formulaList = calResults;
        this.startSimu();
        this.calProbability();
        this.calDistribution();
    }

    /**
     * 按公式计算出第k次成功的概率(k ∈ [1,n]), 写入列表
     */
    private void calDistribution() {

        for (int i = 1; i <= n; i++) {
            formulaList.add(Math.pow(1 - p, i - 1) * p);
        }
    }

    /**
     * 开始模拟实验过程
     */
    private void startSimu() {
        //实例化随机数生成器
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        //Map的key形式为 >> "k=i"
        sb.append("k=");

        //n次独立同分布实验
        for (int j = 0; j < n; j++) {
            //第i次实验
            for (int i = 1; i <= n; i++) {
                double rand = random.nextDouble();
                //成功则改变"第i(k)次成功"的频次(或频次+1)
                if (rand >= 0 && rand <= p) {
                    sb.append(i);
                    //如果包含这个entry, 则使频数+1
                    if (expMap.containsKey(sb.toString())) {
                        expMap.put(sb.toString(), expMap.get(sb.toString()) + 1);
                    } else {
                        //不包含则写入这个entry
                        expMap.put(sb.toString(), 1);
                    }
                    //重复利用
//                    sb.deleteCharAt(2);
                    sb.delete(0, sb.length());
                    sb.append("k=");
                    break;
                }
            }
        }


    }

    /**
     * 计算模拟的每个独立同分布实验的概率
     */
    private void calProbability() {
        StringBuilder sb = new StringBuilder();
        sb.append("k=");
        for (int i = 1; i <= n; i++) {
            sb.append(i);
            //求"第i次成功"的概率
            //如果存在说明"第i次成功"发生过
            if (expMap.containsKey(sb.toString())) {
                expList.add(((double) (expMap.get(sb.toString())) / n));
            } else {
                //如果不存在说明"第i次成功"没有发生过, 概率填入0
                expList.add(0.0);
            }
            sb.delete(0, sb.length());
            sb.append("k=");
        }
    }

    /**
     * 保存文件到txt
     * @param fileName
     * @param list
     */
    private void saveToTxt(String fileName, ArrayList list) {
        StringBuilder sb = new StringBuilder();
        //遍历概率数组, 写入字符串
        for (int i = 1; i <= n; i++) {
            sb.append(i + " " + list.get(i - 1) + "\n");
        }
        FileOutputStream fos = null;
        File file = new File(fileName);
        //如果文件不存在, 就创建文件
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            fos = new FileOutputStream(file);
            fos.write(sb.toString().getBytes());
            fos.flush();
            fos.close();

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

//    public static void main(String[] args) {
////        GeometricDistribution gd = new GeometricDistribution(0.001, 10000);  //0.3 100
//        gd.startSimu();
////        System.out.println(gd.expMap);
////        for (Map.Entry<String, Integer> entry :
////                gd.expMap.entrySet()) {
////            System.out.println("{" + entry.getKey() + ":" + entry.getValue() + "}");
////        }
//        gd.calProbability();
////        System.out.println(gd.expList);
//        gd.calDistribution();
//        //保存模拟仿真结果
//        gd.saveToTxt("ExpResult.txt", gd.expList);
//        //保存公式计算结果
//        gd.saveToTxt("CalResult.txt", gd.formulaList);
//
//    }
}
